src/ostree/ot-builtin-remote.c \
src/ostree/ot-builtin-rev-parse.c \
src/ostree/ot-builtin-show.c \
+ src/ostree/ot-builtin-trivial-httpd.c \
src/ostree/ot-builtin-write-refs.c \
src/ostree/ot-main.h \
src/ostree/ot-main.c \
{ "remote", ostree_builtin_remote, 0 },
{ "rev-parse", ostree_builtin_rev_parse, 0 },
{ "show", ostree_builtin_show, 0 },
+ { "trivial-httpd", ostree_builtin_trivial_httpd, OSTREE_BUILTIN_FLAG_NO_REPO },
{ "write-refs", ostree_builtin_write_refs, 0 },
{ NULL }
};
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <libsoup/soup.h>
+
+#include "ot-builtins.h"
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "ot-main.h"
+#include "ostree.h"
+#include "ostree-repo-file.h"
+
+#include <glib/gi18n.h>
+
+static char *opt_port_file = NULL;
+static gboolean opt_daemonize;
+static gboolean opt_autoexit;
+
+typedef struct {
+ GFile *root;
+ gboolean running;
+} OtTrivialHttpd;
+
+static GOptionEntry options[] = {
+ { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &opt_daemonize, "Fork into background when ready", NULL },
+ { "autoexit", 0, 0, G_OPTION_ARG_NONE, &opt_autoexit, "Automatically exit when directory is deleted", NULL },
+ { "port-file", 'p', 0, G_OPTION_ARG_FILENAME, &opt_port_file, "Write port number to PATH", "PATH" },
+ { NULL }
+};
+
+static int
+compare_strings (gconstpointer a, gconstpointer b)
+{
+ const char **sa = (const char **)a;
+ const char **sb = (const char **)b;
+
+ return strcmp (*sa, *sb);
+}
+
+static GString *
+get_directory_listing (const char *path)
+{
+ GPtrArray *entries;
+ GString *listing;
+ char *escaped;
+ DIR *dir;
+ struct dirent *dent;
+ int i;
+
+ entries = g_ptr_array_new ();
+ dir = opendir (path);
+ if (dir)
+ {
+ while ((dent = readdir (dir)))
+ {
+ if (!strcmp (dent->d_name, ".") ||
+ (!strcmp (dent->d_name, "..") &&
+ !strcmp (path, "./")))
+ continue;
+ escaped = g_markup_escape_text (dent->d_name, -1);
+ g_ptr_array_add (entries, escaped);
+ }
+ closedir (dir);
+ }
+
+ g_ptr_array_sort (entries, (GCompareFunc)compare_strings);
+
+ listing = g_string_new ("<html>\r\n");
+ escaped = g_markup_escape_text (strchr (path, '/'), -1);
+ g_string_append_printf (listing, "<head><title>Index of %s</title></head>\r\n", escaped);
+ g_string_append_printf (listing, "<body><h1>Index of %s</h1>\r\n<p>\r\n", escaped);
+ g_free (escaped);
+ for (i = 0; i < entries->len; i++)
+ {
+ g_string_append_printf (listing, "<a href=\"%s\">%s</a><br>\r\n",
+ (char *)entries->pdata[i],
+ (char *)entries->pdata[i]);
+ g_free (entries->pdata[i]);
+ }
+ g_string_append (listing, "</body>\r\n</html>\r\n");
+
+ g_ptr_array_free (entries, TRUE);
+ return listing;
+}
+
+/* Only allow reading files that have o+r, and for directories, o+x.
+ * This makes this server relatively safe to use on multiuser
+ * machines.
+ */
+static gboolean
+is_safe_to_access (struct stat *stbuf)
+{
+ /* Only regular files or directores */
+ if (!(S_ISREG (stbuf->st_mode) || S_ISDIR (stbuf->st_mode)))
+ return FALSE;
+ /* Must be o+r */
+ if (!(stbuf->st_mode & S_IROTH))
+ return FALSE;
+ /* For directories, must be o+x */
+ if (S_ISDIR (stbuf->st_mode) && !(stbuf->st_mode & S_IXOTH))
+ return FALSE;
+ return TRUE;
+}
+
+static void
+do_get (OtTrivialHttpd *self,
+ SoupServer *server,
+ SoupMessage *msg,
+ const char *path)
+{
+ char *slash;
+ int ret;
+ struct stat stbuf;
+ gs_free char *safepath = NULL;
+
+ if (strstr (path, "../") != NULL)
+ {
+ soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+ goto out;
+ }
+
+ if (path[0] == '/')
+ path++;
+
+ safepath = g_build_filename (gs_file_get_path_cached (self->root), path, NULL);
+
+ do
+ ret = stat (safepath, &stbuf);
+ while (ret == -1 && errno == EINTR);
+ if (ret == -1)
+ {
+ if (errno == EPERM)
+ soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+ else if (errno == ENOENT)
+ soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+ else
+ soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+ goto out;
+ }
+
+ if (!is_safe_to_access (&stbuf))
+ {
+ soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+ goto out;
+ }
+
+ if (S_ISDIR (stbuf.st_mode))
+ {
+ slash = strrchr (safepath, '/');
+ if (!slash || slash[1])
+ {
+ gs_free char *redir_uri = NULL;
+
+ redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path);
+ soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY,
+ redir_uri);
+ }
+ else
+ {
+ gs_free char *index_realpath = g_strconcat (safepath, "/index.html", NULL);
+ if (stat (index_realpath, &stbuf) != -1)
+ {
+ gs_free char *index_path = g_strconcat (path, "/index.html", NULL);
+ do_get (self, server, msg, index_path);
+ }
+ else
+ {
+ GString *listing = get_directory_listing (safepath);
+ soup_message_set_response (msg, "text/html",
+ SOUP_MEMORY_TAKE,
+ listing->str, listing->len);
+ g_string_free (listing, FALSE);
+ }
+ }
+ }
+ else
+ {
+ if (!S_ISREG (stbuf.st_mode))
+ {
+ soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+ goto out;
+ }
+
+ if (msg->method == SOUP_METHOD_GET)
+ {
+ GMappedFile *mapping;
+ SoupBuffer *buffer;
+
+ mapping = g_mapped_file_new (safepath, FALSE, NULL);
+ if (!mapping)
+ {
+ soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+ goto out;
+ }
+
+ buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
+ g_mapped_file_get_length (mapping),
+ mapping, (GDestroyNotify)g_mapped_file_unref);
+ soup_message_body_append_buffer (msg->response_body, buffer);
+ soup_buffer_free (buffer);
+ }
+ else /* msg->method == SOUP_METHOD_HEAD */
+ {
+ gs_free char *length = NULL;
+
+ /* We could just use the same code for both GET and
+ * HEAD (soup-message-server-io.c will fix things up).
+ * But we'll optimize and avoid the extra I/O.
+ */
+ length = g_strdup_printf ("%lu", (gulong)stbuf.st_size);
+ soup_message_headers_append (msg->response_headers,
+ "Content-Length", length);
+ }
+ soup_message_set_status (msg, SOUP_STATUS_OK);
+ }
+ out:
+ return;
+}
+
+static void
+httpd_callback (SoupServer *server, SoupMessage *msg,
+ const char *path, GHashTable *query,
+ SoupClientContext *context, gpointer data)
+{
+ OtTrivialHttpd *self = data;
+ SoupMessageHeadersIter iter;
+
+ soup_message_headers_iter_init (&iter, msg->request_headers);
+
+ if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
+ do_get (self, server, msg, path);
+ else
+ soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+}
+
+static void
+on_dir_changed (GFileMonitor *mon,
+ GFile *file,
+ GFile *other,
+ GFileMonitorEvent event,
+ gpointer user_data)
+{
+ OtTrivialHttpd *self = user_data;
+
+ if (event == G_FILE_MONITOR_EVENT_DELETED)
+ {
+ self->running = FALSE;
+ g_main_context_wakeup (NULL);
+ }
+}
+
+gboolean
+ostree_builtin_trivial_httpd (int argc, char **argv, GFile *repo_path, GError **error)
+{
+ gboolean ret = FALSE;
+ GCancellable *cancellable = NULL;
+ GOptionContext *context;
+ const char *dirpath;
+ OtTrivialHttpd appstruct;
+ OtTrivialHttpd *app = &appstruct;
+ gs_unref_object GFile *dir = NULL;
+ gs_unref_object SoupServer *server = NULL;
+ gs_unref_object GFileMonitor *dirmon = NULL;
+
+ memset (&appstruct, 0, sizeof (appstruct));
+
+ context = g_option_context_new ("[DIR] - Simple webserver");
+
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (argc > 1)
+ dirpath = argv[1];
+ else
+ dirpath = ".";
+
+ app->root = g_file_new_for_path (dirpath);
+
+ server = soup_server_new (SOUP_SERVER_PORT, 0,
+ SOUP_SERVER_SERVER_HEADER, "ostree-httpd ",
+ NULL);
+ soup_server_add_handler (server, NULL, httpd_callback, app, NULL);
+ if (opt_port_file)
+ {
+ gs_free char *portstr = g_strdup_printf ("%u\n", soup_server_get_port (server));
+ if (!g_file_set_contents (opt_port_file, portstr, strlen (portstr), error))
+ goto out;
+ }
+ soup_server_run_async (server);
+
+ if (opt_daemonize)
+ {
+ pid_t pid = fork();
+ if (pid == -1)
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ else if (pid > 0)
+ {
+ /* Parent */
+ _exit (0);
+ }
+ /* Child, continue */
+ }
+
+ app->running = TRUE;
+ if (opt_autoexit)
+ {
+ dirmon = g_file_monitor_directory (app->root, 0, cancellable, error);
+ if (!dirmon)
+ goto out;
+ g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app);
+ }
+
+ while (app->running)
+ g_main_context_iteration (NULL, TRUE);
+
+ ret = TRUE;
+ out:
+ g_clear_object (&app->root);
+ if (context)
+ g_option_context_free (context);
+ return ret;
+}
gboolean ostree_builtin_rev_parse (int argc, char **argv, GFile *repo_path, GError **error);
gboolean ostree_builtin_remote (int argc, char **argv, GFile *repo_path, GError **error);
gboolean ostree_builtin_write_refs (int argc, char **argv, GFile *repo_path, GError **error);
+gboolean ostree_builtin_trivial_httpd (int argc, char **argv, GFile *repo_path, GError **error);
G_END_DECLS
+++ /dev/null
-# Toplevel tests Makefile
-#
-# Copyright (C) 2011 Colin Walters <walters@verbum.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-TESTS = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
-
-all: tmpdir-lifecycle run-apache
-
-tmpdir-lifecycle: tmpdir-lifecycle.c Makefile
- gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $<
-
-run-apache: run-apache.c Makefile
- gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $<
-
-check:
- @for test in $(TESTS); do \
- echo $$test; \
- ./$$test; \
- done
-
}
setup_fake_remote_repo1() {
- if ! test -x ${SRCDIR}/run-apache; then
- exit 77
- fi
mode=$1
shift
oldpwd=`pwd`
cd ${test_tmpdir}
mkdir ${test_tmpdir}/httpd
cd httpd
- cat >httpd.conf <<EOF
-ServerRoot ${test_tmpdir}/httpd
-PidFile pid
-LogLevel crit
-ErrorLog log
-LockFile lock
-ServerName localhost
-
-LoadModule alias_module modules/mod_alias.so
-LoadModule cgi_module modules/mod_cgi.so
-LoadModule env_module modules/mod_env.so
-
-StartServers 1
-
-# SetEnv OSTREE_REPO_PREFIX ${test_tmpdir}/ostree-srv
-Alias /ostree/ ${test_tmpdir}/ostree-srv/
-# ScriptAlias /ostree/ ${test_tmpdir}/httpd/ostree-http-backend/
-EOF
- ${SRCDIR}/tmpdir-lifecycle ${SRCDIR}/run-apache `pwd`/httpd.conf ${test_tmpdir}/httpd-address &
- for i in $(seq 5); do
- if ! test -f ${test_tmpdir}/httpd-address; then
- sleep 1
- else
- break
- fi
- done
- if ! test -f ${test_tmpdir}/httpd-address; then
- echo "Error: timed out waiting for httpd-address file"
- exit 1
- fi
+ ln -s ${test_tmpdir}/ostree-srv ostree
+ ostree trivial-httpd --daemonize -p ${test_tmpdir}/httpd-port
+ port=$(cat ${test_tmpdir}/httpd-port)
+ echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
cd ${oldpwd}
export OSTREE="ostree --repo=repo"
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include <gio/gio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <sys/stat.h>
-
-/* Taken from gnome-user-share src/httpd.c under the GPLv2 */
-static int
-get_port (void)
-{
- int sock;
- int saved_errno;
- struct sockaddr_in addr;
- int reuse;
- socklen_t len;
-
- sock = socket (PF_INET, SOCK_STREAM, 0);
- if (sock < 0)
- {
- return -1;
- }
-
- memset (&addr, 0, sizeof (addr));
- addr.sin_family = AF_INET;
- addr.sin_port = 0;
- addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
-
- reuse = 1;
- setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
- if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) == -1)
- {
- saved_errno = errno;
- close (sock);
- errno = saved_errno;
- return -1;
- }
-
- len = sizeof (addr);
- if (getsockname (sock, (struct sockaddr *)&addr, &len) == -1)
- {
- saved_errno = errno;
- close (sock);
- errno = saved_errno;
- return -1;
- }
-
-#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
- /* XXX This exposes a potential race condition, but without this,
- * httpd will not start on the above listed platforms due to the fact
- * that SO_REUSEADDR is also needed when Apache binds to the listening
- * socket. At this time, Apache does not support that socket option.
- */
- close (sock);
-#endif
- return ntohs (addr.sin_port);
-}
-
-static const char *known_httpd_modules_locations [] = {
- "/usr/libexec/apache2",
- "/usr/lib/apache2/modules",
- "/usr/lib64/httpd/modules",
- "/usr/lib/httpd/modules",
- NULL
-};
-
-static gchar*
-get_httpd_modules_path ()
-{
- int i;
-
- for (i = 0; known_httpd_modules_locations[i]; i++)
- {
- if (g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_EXECUTABLE)
- && g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_DIR))
- {
- return g_strdup (known_httpd_modules_locations[i]);
- }
- }
- return NULL;
-}
-
-int
-main (int argc,
- char **argv)
-{
- int port;
- char *listen;
- char *address_string;
- GError *error = NULL;
- GPtrArray *httpd_argv;
- char *modules;
-
- if (argc != 3)
- {
- fprintf (stderr, "usage: run-apache CONF PORTFILE");
- return 1;
- }
-
- g_type_init ();
-
- port = get_port ();
- if (port == -1)
- {
- perror ("Failed to bind port");
- return 1;
- }
-
- httpd_argv = g_ptr_array_new ();
- g_ptr_array_add (httpd_argv, "httpd");
- g_ptr_array_add (httpd_argv, "-DFOREGROUND");
- g_ptr_array_add (httpd_argv, "-f");
- g_ptr_array_add (httpd_argv, argv[1]);
- g_ptr_array_add (httpd_argv, "-C");
- listen = g_strdup_printf ("Listen 127.0.0.1:%d", port);
- g_ptr_array_add (httpd_argv, listen);
- g_ptr_array_add (httpd_argv, NULL);
-
- address_string = g_strdup_printf ("http://127.0.0.1:%d\n", port);
-
- if (!g_file_set_contents (argv[2], address_string, -1, &error))
- {
- g_printerr ("%s\n", error->message);
- return 1;
- }
-
- setenv ("LANG", "C", 1);
- modules = get_httpd_modules_path ();
- if (modules == NULL)
- {
- g_printerr ("Failed to find httpd modules\n");
- return 1;
- }
- if (symlink (modules, "modules") < 0)
- {
- perror ("failed to make modules symlink");
- return 1;
- }
- execvp ("httpd", (char**)httpd_argv->pdata);
- perror ("Failed to run httpd");
- return 1;
-}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Kill a child process when the current directory is deleted
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include <gio/gio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-
-struct TmpdirLifecyleData {
- GMainLoop *loop;
- GPid pid;
- gboolean exited;
-};
-
-static void
-on_dir_changed (GFileMonitor *mon,
- GFile *file,
- GFile *other,
- GFileMonitorEvent event,
- gpointer user_data)
-{
- struct TmpdirLifecyleData *data = user_data;
-
- if (event == G_FILE_MONITOR_EVENT_DELETED)
- g_main_loop_quit (data->loop);
-}
-
-static void
-on_child_exited (GPid pid,
- int status,
- gpointer user_data)
-{
- struct TmpdirLifecyleData *data = user_data;
-
- data->exited = TRUE;
- g_main_loop_quit (data->loop);
-}
-
-int
-main (int argc,
- char **argv)
-{
- GFileMonitor *monitor;
- GFile *curdir;
- GError *error = NULL;
- GPtrArray *new_argv;
- int i;
- struct TmpdirLifecyleData data;
-
- g_type_init ();
-
- memset (&data, 0, sizeof (data));
-
- data.loop = g_main_loop_new (NULL, TRUE);
-
- curdir = g_file_new_for_path (".");
- monitor = g_file_monitor_directory (curdir, 0, NULL, &error);
- if (!monitor)
- exit (1);
- g_signal_connect (monitor, "changed",
- G_CALLBACK (on_dir_changed), &data);
-
- new_argv = g_ptr_array_new ();
- for (i = 1; i < argc; i++)
- g_ptr_array_add (new_argv, argv[i]);
- g_ptr_array_add (new_argv, NULL);
-
- if (!g_spawn_async (NULL, (char**)new_argv->pdata, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
- NULL, NULL, &data.pid, &error))
- {
- g_printerr ("%s\n", error->message);
- return 1;
- }
- g_child_watch_add (data.pid, on_child_exited, &data);
-
- g_main_loop_run (data.loop);
-
- if (!data.exited)
- kill (data.pid, SIGTERM);
-
- return 0;
-}